#include	"dat.h"
#include	"fns.h"
#include	"error.h"

/* 
   TODO:
       move data to stdio and also provide stdin/stdout
       make sure wait behavior is similar to xcpufs
       add argv, env, ns, and fs files
       open question as to whether to keep exec or fold it into ctl args
       same could be said about the ns and env files really...
       fs file won't be a directory anymore, it'll be a pipe
       
       top level directory needs arch, env, state?, procs?, we talked about
       other metadata stuff -- but I think these will be handled by the taskfs
       and not the back end.  I'm also thinking that toplevel will have an fs
       which points to the local file system on the node and we may want to
       keep some additional accounting info and/or status information.
       
       state is an interesting question - we'll need to have something here
       representative of how much is running on the node.
       
       its possible we may want multi-views -- the fs file will be a pipe
       for the client but a mounted directory on the server?  That will really
       be for later anyways.
       
       Overall this shouldn't be a big deal to implement the interfaces --
       the hard part will be taking care of the mount point stuff.
       We can setup the namespace in the generic code, and let the
       platform specific code handle the backmount.
       
       The other aspect worth considering here is enumeration of children.
       This seems like it would be much more simple if either the children
       or the local interfaces were under a directory.  I think it would make 
       the most sense to have a local directory under which the local file
       system and/or exported central services resided.
*/ 
       

enum
{
	Qtopdir,	/* top level directory */
	Qcmd,
	Qclonus,
	Qlocalfs,   /* local fs mount point */
	Qarch,	/* architecture description */
	Qtopns,	/* default ns for host */
	Qtopenv,	/* default env for host */
	Qtopstat,	/* Monitoring Information for host */
	Qconvdir,
	Qconvbase,
	Qdata = Qconvbase,
	Qstderr,
	Qctl,
	Qstatus,
	Qargs,
	Qenv,
	Qstdin,
	Qstdout,
	Qns,
	Qwait,

	Debug=0	/* to help debug os.c */
};
#define TYPE(x) 	((ulong)(x).path & 0xff)
#define CONV(x) 	(((ulong)(x).path >> 8)&0xfff)
#define QID(c, y) 	(((c)<<8) | (y))

typedef struct Conv	Conv;
struct Conv
{
	int	x;
	int	inuse;
	int	fd[3];	/* stdin, stdout, and stderr */
	int	count[3];	/* number of readers on stdin/stdout/stderr */
	int	perm;
	char*	owner;
	char*	state;
	Cmdbuf*	cmd;
	char*	dir;
	QLock	l;	/* protects state changes */
	Queue*	waitq;
	void*	child;
	char*	error;	/* on start up */
	int	nice;
	short	killonclose;
	short	killed;
	Rendez	startr;
};

static struct
{
	QLock	l;
	int	nc;
	int	maxconv;
	Conv**	conv;
} cmd;

static	Conv*	cmdclone(char*);
static	void	cmdproc(void*);

static int
cmd3gen(Chan *c, int i, Dir *dp)
{
	Qid q;
	Conv *cv;

	cv = cmd.conv[CONV(c->qid)];
	switch(i){
	default:
		return -1;
	case Qdata:
		mkqid(&q, QID(CONV(c->qid), Qdata), 0, QTFILE);
		devdir(c, q, "stdio", 0, cv->owner, cv->perm, dp);
		return 1;
	case Qstderr:
		mkqid(&q, QID(CONV(c->qid), Qstderr), 0, QTFILE);
		devdir(c, q, "stderr", 0, cv->owner, 0444, dp);
		return 1;
	case Qctl:
		mkqid(&q, QID(CONV(c->qid), Qctl), 0, QTFILE);
		devdir(c, q, "ctl", 0, cv->owner, cv->perm, dp);
		return 1;
	case Qstatus:
		mkqid(&q, QID(CONV(c->qid), Qstatus), 0, QTFILE);
		devdir(c, q, "status", 0, cv->owner, 0444, dp);
		return 1;
	case Qwait:
		mkqid(&q, QID(CONV(c->qid), Qwait), 0, QTFILE);
		devdir(c, q, "wait", 0, cv->owner, 0444, dp);
		return 1;
	case Qstdin:
		mkqid(&q, QID(CONV(c->qid), Qstdin), 0, QTFILE);
		devdir(c, q, "stdin", 0, cv->owner, 0222, dp);
		return 1;
	case Qstdout:
		mkqid(&q, QID(CONV(c->qid), Qstdout), 0, QTFILE);
		devdir(c, q, "stdout", 0, cv->owner, 0444, dp);
		return 1;
	case Qargs:
		mkqid(&q, QID(CONV(c->qid), Qstdin), 0, QTFILE);
		devdir(c, q, "args", 0, cv->owner, cv->perm, dp);
		return 1;	
	case Qenv:
		mkqid(&q, QID(CONV(c->qid), Qstdin), 0, QTFILE);
		devdir(c, q, "env", 0, cv->owner, cv->perm, dp);
		return 1;	
	case Qns:
		mkqid(&q, QID(CONV(c->qid), Qns), 0, QTFILE);
		devdir(c, q, "ns", 0, cv->owner, cv->perm, dp);
		return 1;						
	}
}

static int
cmdgen(Chan *c, char *name, Dirtab *d, int nd, int s, Dir *dp)
{
	Qid q;
	Conv *cv;

	USED(name);
	USED(nd);
	USED(d);

	if(s == DEVDOTDOT){
		switch(TYPE(c->qid)){
		case Qtopdir:
		case Qcmd:
			mkqid(&q, QID(0, Qtopdir), 0, QTDIR);
			devdir(c, q, "#2", 0, eve, DMDIR|0555, dp);
			break;
		case Qconvdir:
			mkqid(&q, QID(0, Qcmd), 0, QTDIR);
			devdir(c, q, "local", 0, eve, DMDIR|0555, dp);
			break;
		default:
			panic("cmdgen %llux", c->qid.path);
		}
		return 1;
	}

	switch(TYPE(c->qid)) {
	case Qtopdir:
		if(s >= 1)
			return -1;
		mkqid(&q, QID(0, Qcmd), 0, QTDIR);
		devdir(c, q, "local", 0, eve, DMDIR|0555, dp);
		return 1;
	case Qcmd:
		if(s < cmd.nc) {
			cv = cmd.conv[s];
			mkqid(&q, QID(s, Qconvdir), 0, QTDIR);
			sprint(up->genbuf, "%d", s);
			devdir(c, q, up->genbuf, 0, cv->owner, DMDIR|0555, dp);
			return 1;
		}
		s -= cmd.nc;
		if(s == 0){
			mkqid(&q, QID(0, Qclonus), 0, QTFILE);
			devdir(c, q, "clone", 0, eve, 0666, dp);
			return 1;
		}
		if(s == 1){
			mkqid(&q, QID(0, Qlocalfs), 0, QTDIR);
			devdir(c, q, "fs", 0, eve, DMDIR|0555, dp);
			return 1;
		}
		if(s == 2){
			mkqid(&q, QID(0, Qarch), 0, QTDIR);
			devdir(c, q, "arch", 0, eve, 0444, dp);
			return 1;
		}
		if(s == 3){
			mkqid(&q, QID(0, Qtopns), 0, QTDIR);
			devdir(c, q, "ns", 0, eve, 0666, dp);
			return 1;
		}
		if(s == 4){
			mkqid(&q, QID(0, Qtopenv), 0, QTDIR);
			devdir(c, q, "env", 0, eve, 0666, dp);
			return 1;
		}
		if(s == 5){
			mkqid(&q, QID(0, Qtopstat), 0, QTDIR);
			devdir(c, q, "status", 0, eve, 0444, dp);
			return 1;
		}				
		return -1;
	case Qclonus:
		if(s == 0){
			mkqid(&q, QID(0, Qclonus), 0, QTFILE);
			devdir(c, q, "clone", 0, eve, 0666, dp);
			return 1;
		}
		return -1;
	case Qlocalfs:
		if(s == 0){
			mkqid(&q, QID(0, Qlocalfs), 0, QTDIR);
			devdir(c, q, "fs", 0, eve, DMDIR|0555, dp);
			return 1;
		}
		return -1;		
	case Qconvdir:
		return cmd3gen(c, Qconvbase+s, dp);
	case Qdata:
	case Qstderr:
	case Qctl:
	case Qstatus:
	case Qwait:
	case Qstdin:
	case Qstdout:
	case Qenv:
	case Qns:
	case Qargs:
		return cmd3gen(c, TYPE(c->qid), dp);
	}
	return -1;
}

static void
cmdinit(void)
{
	cmd.maxconv = 1000;
	cmd.conv = mallocz(sizeof(Conv*)*(cmd.maxconv+1), 1);
	/* cmd.conv is checked by cmdattach, below */
}

static Chan *
cmdattach(char *spec)
{
	Chan *c;

	if(cmd.conv == nil)
		error(Enomem);
	c = devattach('2', spec);
	mkqid(&c->qid, QID(0, Qtopdir), 0, QTDIR);
	return c;
}

static Walkqid*
cmdwalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, 0, 0, cmdgen);
}

static int
cmdstat(Chan *c, uchar *db, int n)
{
	return devstat(c, db, n, 0, 0, cmdgen);
}

static Chan *
cmdopen(Chan *c, int omode)
{
	int perm;
	Conv *cv;
	char *user;

	perm = 0;
	omode = openmode(omode);
	switch(omode) {
	case OREAD:
		perm = 4;
		break;
	case OWRITE:
		perm = 2;
		break;
	case ORDWR:
		perm = 6;
		break;
	}

	switch(TYPE(c->qid)) {
	default:
		break;
	case Qarch:
	case Qtopns:
	case Qtopenv:
	case Qtopstat:
	case Qstdin:
	case Qstdout:
	case Qenv:
	case Qargs:
	case Qns:
		error(Egreg);	/* TODO: IMPLEMENT */
		break;	
	case Qtopdir:
	case Qcmd:
	case Qconvdir:
	case Qstatus:
		if(omode != OREAD)
			error(Eperm);
		break;
	case Qlocalfs:
		error(Eperm);
		break;
	case Qclonus:
		qlock(&cmd.l);
		if(waserror()){
			qunlock(&cmd.l);
			nexterror();
		}
		cv = cmdclone(up->env->user);
		poperror();
		qunlock(&cmd.l);
		if(cv == 0)
			error(Enodev);
		mkqid(&c->qid, QID(cv->x, Qctl), 0, QTFILE);
		break;
	case Qdata:
	case Qstderr:
	case Qctl:
	case Qwait:
		qlock(&cmd.l);
		cv = cmd.conv[CONV(c->qid)];
		qlock(&cv->l);
		if(waserror()){
			qunlock(&cv->l);
			qunlock(&cmd.l);
			nexterror();
		}
		user = up->env->user;
		if((perm & (cv->perm>>6)) != perm) {
			if(strcmp(user, cv->owner) != 0 ||
		 	  (perm & cv->perm) != perm)
				error(Eperm);
		}
		switch(TYPE(c->qid)){
		case Qdata:
			if(omode == OWRITE || omode == ORDWR)
				cv->count[0]++;
			if(omode == OREAD || omode == ORDWR)
				cv->count[1]++;
			break;
		case Qstderr:
			if(omode != OREAD)
				error(Eperm);
			cv->count[2]++;
			break;
		case Qwait:
			if(cv->waitq == nil)
				cv->waitq = qopen(1024, Qmsg, nil, 0);
			break;
		}
		cv->inuse++;
		if(cv->inuse == 1) {
			cv->state = "Open";
			kstrdup(&cv->owner, user);
			cv->perm = 0660;
			cv->nice = 0;
		}
		poperror();
		qunlock(&cv->l);
		qunlock(&cmd.l);
		break;
	}
	c->mode = omode;
	c->flag |= COPEN;
	c->offset = 0;
	return c;
}

static void
closeconv(Conv *c)
{
	kstrdup(&c->owner, "cmd");
	kstrdup(&c->dir, rootdir);
	c->perm = 0666;
	c->state = "Closed";
	c->killonclose = 0;
	c->killed = 0;
	c->nice = 0;
	free(c->cmd);
	c->cmd = nil;
	if(c->waitq != nil){
		qfree(c->waitq);
		c->waitq = nil;
	}
	free(c->error);
	c->error = nil;
}

static void
cmdfdclose(Conv *c, int fd)
{
	if(--c->count[fd] == 0 && c->fd[fd] != -1){
		close(c->fd[fd]);
		c->fd[fd] = -1;
	}
}

static void
cmdclose(Chan *c)
{
	Conv *cc;
	int r;

	if((c->flag & COPEN) == 0)
		return;

	switch(TYPE(c->qid)) {
	case Qctl:
	case Qdata:
	case Qstderr:
	case Qwait:
		cc = cmd.conv[CONV(c->qid)];
		qlock(&cc->l);
		if(TYPE(c->qid) == Qdata){
			if(c->mode == OWRITE || c->mode == ORDWR)
				cmdfdclose(cc, 0);
			if(c->mode == OREAD || c->mode == ORDWR)
				cmdfdclose(cc, 1);
		}else if(TYPE(c->qid) == Qstderr)
			cmdfdclose(cc, 2);

		r = --cc->inuse;
		if(cc->child != nil){
			if(!cc->killed)
			if(r == 0 || (cc->killonclose && TYPE(c->qid) == Qctl)){
				oscmdkill(cc->child);
				cc->killed = 1;
			}
		}else if(r == 0)
			closeconv(cc);

		qunlock(&cc->l);
		break;
	}
}

static long
cmdread(Chan *ch, void *a, long n, vlong offset)
{
	Conv *c;
	char *p, *cmds;
	int fd;

	USED(offset);

	p = a;
	switch(TYPE(ch->qid)) {
	default:
		error(Eperm);
	case Qcmd:
	case Qtopdir:
	case Qconvdir:
		return devdirread(ch, a, n, 0, 0, cmdgen);
	case Qctl:
		sprint(up->genbuf, "%ld", CONV(ch->qid));
		return readstr(offset, p, n, up->genbuf);
	case Qstatus:
		c = cmd.conv[CONV(ch->qid)];
		cmds = "";
		if(c->cmd != nil)
			cmds = c->cmd->f[1];
		snprint(up->genbuf, sizeof(up->genbuf), "cmd/%d %d %s %q %q\n",
			c->x, c->inuse, c->state, c->dir, cmds);
		return readstr(offset, p, n, up->genbuf);
	case Qdata:
	case Qstderr:
		fd = 1;
		if(TYPE(ch->qid) == Qstderr)
			fd = 2;
		c = cmd.conv[CONV(ch->qid)];
		qlock(&c->l);
		if(c->fd[fd] == -1){
			qunlock(&c->l);
			return 0;
		}
		qunlock(&c->l);
		osenter();
		n = read(c->fd[fd], a, n);
		osleave();
		if(n < 0)
			oserror();
		return n;
	case Qwait:
		c = cmd.conv[CONV(ch->qid)];
		return qread(c->waitq, a, n);
	}
}

static int
cmdstarted(void *a)
{
	Conv *c;

	c = a;
	return c->child != nil || c->error != nil || strcmp(c->state, "Execute") != 0;
}

enum
{
	CMdir,
	CMexec,
	CMkill,
	CMnice,
	CMkillonclose
};

static
Cmdtab cmdtab[] = {
	CMdir,	"dir",	2,
	CMexec,	"exec",	0,
	CMkill,	"kill",	1,
	CMnice,	"nice",	0,
	CMkillonclose, "killonclose", 0,
};

static long
cmdwrite(Chan *ch, void *a, long n, vlong offset)
{
	int i, r;
	Conv *c;
	Cmdbuf *cb;
	Cmdtab *ct;

	USED(offset);

	switch(TYPE(ch->qid)) {
	default:
		error(Eperm);
	case Qctl:
		c = cmd.conv[CONV(ch->qid)];
		cb = parsecmd(a, n);
		if(waserror()){
			free(cb);
			nexterror();
		}
		ct = lookupcmd(cb, cmdtab, nelem(cmdtab));
		switch(ct->index){
		case CMdir:
			kstrdup(&c->dir, cb->f[1]);
			break;
		case CMexec:
			poperror();	/* cb */
			qlock(&c->l);
			if(waserror()){
				qunlock(&c->l);
				free(cb);
				nexterror();
			}
			if(c->child != nil || c->cmd != nil)
				error(Einuse);
			for(i = 0; i < nelem(c->fd); i++)
				if(c->fd[i] != -1)
					error(Einuse);
			if(cb->nf < 1)
				error(Etoosmall);
			kproc("cmdproc", cmdproc, c, 0);	/* cmdproc held back until unlock below */
			free(c->cmd);
			c->cmd = cb;	/* don't free cb */
			c->state = "Execute";
			poperror();
			qunlock(&c->l);
			while(waserror())
				;
			Sleep(&c->startr, cmdstarted, c);
			poperror();
			if(c->error)
				error(c->error);
			return n;	/* avoid free(cb) below */
		case CMkill:
			qlock(&c->l);
			if(waserror()){
				qunlock(&c->l);
				nexterror();
			}
			if(c->child == nil)
				error("not started");
			if(oscmdkill(c->child) < 0)
				oserror();
			poperror();
			qunlock(&c->l);
			break;
		case CMnice:
			c->nice = cb->nf > 1? atoi(cb->f[1]): 1;
			break;
		case CMkillonclose:
			c->killonclose = 1;
			break;
		}
		poperror();
		free(cb);
		break;
	case Qdata:
		c = cmd.conv[CONV(ch->qid)];
		qlock(&c->l);
		if(c->fd[0] == -1){
			qunlock(&c->l);
			error(Ehungup);
		}
		qunlock(&c->l);
		osenter();
		r = write(c->fd[0], a, n);
		osleave();
		if(r == 0)
			error(Ehungup);
		if(r < 0) {
			/* XXX perhaps should kill writer "write on closed pipe" here, 2nd time around? */
			oserror();
		}
		return r;
	}
	return n;
}

static int
cmdwstat(Chan *c, uchar *dp, int n)
{
	Dir *d;
	Conv *cv;

	switch(TYPE(c->qid)){
	default:
		error(Eperm);
	case Qctl:
	case Qdata:
	case Qstderr:
		d = malloc(sizeof(*d)+n);
		if(d == nil)
			error(Enomem);
		if(waserror()){
			free(d);
			nexterror();
		}
		n = convM2D(dp, n, d, (char*)&d[1]);
		if(n == 0)
			error(Eshortstat);
		cv = cmd.conv[CONV(c->qid)];
		if(!iseve() && strcmp(up->env->user, cv->owner) != 0)
			error(Eperm);
		if(!emptystr(d->uid))
			kstrdup(&cv->owner, d->uid);
		if(d->mode != ~0UL)
			cv->perm = d->mode & 0777;
		poperror();
		free(d);
		break;
	}
	return n;
}

static Conv*
cmdclone(char *user)
{
	Conv *c, **pp, **ep;
	int i;

	c = nil;
	ep = &cmd.conv[cmd.maxconv];
	for(pp = cmd.conv; pp < ep; pp++) {
		c = *pp;
		if(c == nil) {
			c = malloc(sizeof(Conv));
			if(c == nil)
				error(Enomem);
			qlock(&c->l);
			c->inuse = 1;
			c->x = pp - cmd.conv;
			cmd.nc++;
			*pp = c;
			break;
		}
		if(canqlock(&c->l)){
			if(c->inuse == 0 && c->child == nil)
				break;
			qunlock(&c->l);
		}
	}
	if(pp >= ep)
		return nil;

	c->inuse = 1;
	kstrdup(&c->owner, user);
	kstrdup(&c->dir, rootdir);
	c->perm = 0660;
	c->state = "Closed";
	for(i=0; i<nelem(c->fd); i++)
		c->fd[i] = -1;

	qunlock(&c->l);
	return c;
}

static void
cmdproc(void *a)
{
	Conv *c;
	int n;
	char status[ERRMAX];
	void *t;

	c = a;
	qlock(&c->l);
	if(Debug)
		print("f[0]=%q f[1]=%q\n", c->cmd->f[0], c->cmd->f[1]);
	if(waserror()){
		if(Debug)
			print("failed: %q\n", up->env->errstr);
		kstrdup(&c->error, up->env->errstr);
		c->state = "Done";
		qunlock(&c->l);
		Wakeup(&c->startr);
		pexit("cmdproc", 0);
	}
	t = oscmd(c->cmd->f+1, c->nice, c->dir, c->fd);
	if(t == nil)
		oserror();
	c->child = t;	/* to allow oscmdkill */
	poperror();
	qunlock(&c->l);
	Wakeup(&c->startr);
	if(Debug)
		print("started\n");
	while(waserror())
		oscmdkill(t);
	osenter();
	n = oscmdwait(t, status, sizeof(status));
	osleave();
	if(n < 0){
		oserrstr(up->genbuf, sizeof(up->genbuf));
		n = snprint(status, sizeof(status), "0 0 0 0 %q", up->genbuf);
	}
	qlock(&c->l);
	c->child = nil;
	oscmdfree(t);
	if(Debug){
		status[n]=0;
		print("done %d %d %d: %q\n", c->fd[0], c->fd[1], c->fd[2], status);
	}
	if(c->inuse > 0){
		c->state = "Done";
		if(c->waitq != nil)
			qproduce(c->waitq, status, n);
	}else
		closeconv(c);
	qunlock(&c->l);
	pexit("", 0);
}

Dev cmd2devtab = {
	'2',
	"cmd2",

	cmdinit,
	cmdattach,
	cmdwalk,
	cmdstat,
	cmdopen,
	devcreate,
	cmdclose,
	cmdread,
	devbread,
	cmdwrite,
	devbwrite,
	devremove,
	cmdwstat
};
